package com.xh.easyretry.strategy.retry;

import com.xh.easyretry.annotations.TimeAvoidance;
import com.xh.easyretry.annotations.XHRetry;
import com.xh.easyretry.common.XHRetryContext;
import com.xh.easyretry.common.XHRetryResult;
import com.xh.easyretry.common.XHTimeAvoidanceContext;
import com.xh.easyretry.handler.ThreadHandler;
import com.xh.easyretry.strategy.time.TimeAvoidanceStrategy;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.util.Collections;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;

import static com.xh.easyretry.common.XHRetryConstants.DEFAULT_DELAY_STRATEGY;


@Slf4j
@Component
public abstract class AbstractRetryStrategy implements BaseRetryStrategy, ApplicationContextAware {

    private final ThreadHandler threadHandler = ThreadHandler.getInstance();

    private Map<String, TimeAvoidanceStrategy> timeAvoidanceStrategyMap;

    /**
     * 判断是否可以重试
     *
     * @param retryContext 重试上下文
     * @return boolean 本次是否可以执行
     */
    @Override
    public boolean canExecRetry(XHRetryContext retryContext) {
        if (Objects.isNull(retryContext)) {
            log.error("act=canExecRetry msg=未获取到上下文信息，不可进行重试");
            throw new IllegalArgumentException("未获取到上下文信息，不可进行重试");
        }
        XHRetry xhRetry = retryContext.getXhRetry();
        Integer currentRetryCount = retryContext.getCurrentRetryCount();
        int maxRetryCount = xhRetry.retryCount();
        if (currentRetryCount > maxRetryCount) {
            log.info("act=canExecRetry msg=当前重试次数={} 超过最大重试次数，不再重试", currentRetryCount);
            return false;
        }
        retryContext.accept();
        timeAvoidance(retryContext);
        log.info("act=canExecRetry msg=当前重试次数={} 将进行重试", currentRetryCount);
        return true;
    }

    @Override
    public void retryFailed(XHRetryContext retryContext, Object result) {
        Integer currentRetryCount = retryContext.getCurrentRetryCount();
        XHRetryResult retryResult = new XHRetryResult(retryContext, result, currentRetryCount);
        retryResult.accept();

        currentRetryCount++;
        retryContext.setCurrentRetryCount(currentRetryCount);
    }

    @Override
    public Object buildResult(XHRetryContext retryContext) {
        if (Objects.isNull(retryContext)) {
            log.error("act=buildResult msg=未获取到上下文信息，无法构建返回值");
            throw new IllegalArgumentException("未获取到上下文信息，无法构建返回值");
        }
        Class<?> returnType = retryContext.getMethod().getReturnType();
        if (Objects.equals(returnType, Boolean.TYPE)) {
            return false;
        }
        return returnType.isPrimitive() ? 0 : null;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        Map<String, TimeAvoidanceStrategy> string2TimeAvoidanceStrategyMap = applicationContext.getBeansOfType(TimeAvoidanceStrategy.class);
        this.timeAvoidanceStrategyMap = Optional.of(string2TimeAvoidanceStrategyMap)
                .map(Map::values)
                .orElse(Collections.emptyList())
                .stream()
                .collect(Collectors.toMap(TimeAvoidanceStrategy::getStrategy, Function.identity()));
    }

    /**
     * 时间规避
     *
     * @param retryContext 重试上下文
     */
    private void timeAvoidance(XHRetryContext retryContext) {
        XHRetry xhRetry = retryContext.getXhRetry();
        TimeAvoidance timeAvoidance = xhRetry.timeAvoidance();
        String avoidance = StringUtils.hasText(timeAvoidance.avoidance()) ?
                timeAvoidance.avoidance() : DEFAULT_DELAY_STRATEGY;
        TimeAvoidanceStrategy timeAvoidanceStrategy = timeAvoidanceStrategyMap.get(avoidance);
        if (Objects.isNull(timeAvoidanceStrategy)) {
            throw new IllegalArgumentException("time avoidance strategy not found: " + avoidance);
        }
        XHTimeAvoidanceContext timeAvoidanceContext = retryContext.getTimeAvoidanceContext();
        if (Objects.isNull(timeAvoidanceContext)) {
            timeAvoidanceContext = timeAvoidanceStrategy.start(retryContext);
        }
        long delayTime = timeAvoidanceStrategy.calculateDelayTime(retryContext);
        timeAvoidanceContext.setInterval(delayTime);
        try {
            threadHandler.threadSleep(delayTime);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}
